<script lang="ts" setup name="ProTable">
import Sortable from 'sortablejs'
import type { ElTable } from 'element-plus'
import { generateUUID } from '@/utils'
import { useTable } from '@/hooks/useTable'
import { useSelection } from '@/hooks/useSelection'
import { isType } from '@/utils'
import type { ColumnItem, SearchItem } from './interface'
import { useThemeStore } from '@/stores/modules/theme'

export interface ProTableProps {
  api?: (params: any) => Promise<any> //请求表格数据的api
  data?: any[] //静态数据，若存在则不会使用 api 返回的 data
  columns: ColumnItem[] // 列配置项
  searchForm?: SearchItem[] // 搜索条件配置项
  extraParam?: any // 额外参数，防止重置（默认为{}）
  requestAuto?: boolean // 否自动执行请求 api（默认为true）
  dataFormatter?: (data: any, currentTime?: string) => any //返回数据处理
  isPagination?: boolean //是否分页（默认为true）
  rowKey?: string //行数据的 Key 优化渲染 可用于多选（默认为 id）
  isFull?: boolean //是否填满
}

const props = withDefaults(defineProps<ProTableProps>(), {
  columns: () => [],
  searchForm: () => [],
  extraParam: {},
  requestAuto: true,
  isPagination: true,
  rowKey: 'id',
  isFull: false
})

const themeStore = useThemeStore()

const className = computed(() => `pro-table ${props.isFull ? 'pro-table-full' : ''}`)
const size = computed(() => themeStore.themeConfig.componentSize)

/** table 实例 */
const tableRef = ref<InstanceType<typeof ElTable>>()

/** 生成组件唯一id（主要用于表格拖拽排序） */
const uuid = ref('id-' + generateUUID())

/** 表格多选 Hooks */
const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(props.rowKey)

/** 表格操作 Hooks */
const {
  tableData,
  pageParam,
  searchParam,
  searchInitParam,
  loading,
  searchDrawerVisible,
  getTableList,
  search,
  reset,
  handleSizeChange,
  handleCurrentChange
} = useTable(props.api, props.extraParam, props.isPagination, props.dataFormatter)

/** 处理后表格数据（请求数据、本地数据、是否分页） */
const processTableData = computed(() => {
  if (!props.data) return tableData.value
  if (!props.isPagination) return props.data
  return props.data.slice(
    (pageParam.value.pageNo - 1) * pageParam.value.pageSize,
    pageParam.value.pageSize * pageParam.value.pageNo
  )
})

/** 接收 columns 并设置为响应式 */
const tableColumns = reactive<ColumnItem[]>(props.columns)

const enumMap = ref(new Map<string, { [key: string]: any }[]>())
/** 设置 enumMap 存储 enum 值 */
const setEnumMap = async (item: SearchItem) => {
  if (!item.enum) return
  // 如果当前 enumMap 存在相同的值 return
  if (enumMap.value.has(item.key!)) return
  // 当前 enum 为静态数据，则直接存储到 enumMap
  if (isType(item.enum) !== 'function') return enumMap.value.set(item.key!, unref(item.enum!))
  // 为了防止接口执行慢，而存储慢，导致重复请求，所以预先存储为[]，接口返回后再二次存储
  enumMap.value.set(item.key!, [])
  // 当前 enum 为后台数据需要请求数据，则调用该请求接口，并存储到 enumMap
  const { data } = await item.enum()
  // 接口数据可能是分页数据，需要取出 data.data
  enumMap.value.set(item.key!, isType(data) === 'object' ? data.data : data)
}
provide('enumMap', enumMap)

/** 设置 searchParam 初始值 */
props.searchForm.forEach((item) => {
  setEnumMap(item)
  const key = item?.key
  const defaultValue = item?.defaultValue
  if (defaultValue !== undefined && defaultValue !== null) {
    searchParam.value[key] = defaultValue
    searchInitParam.value[key] = defaultValue
  }
})

const emit = defineEmits(['dragSort'])
/** 表格拖拽排序 */
const dragSort = () => {
  const tbody = document.querySelector(`#${uuid.value} tbody`) as HTMLElement
  Sortable.create(tbody, {
    handle: '.table-drag-btn',
    animation: 300,
    onEnd({ newIndex, oldIndex }) {
      const [removedItem] = processTableData.value.splice(oldIndex!, 1)
      processTableData.value.splice(newIndex!, 0, removedItem)
      emit('dragSort', { newIndex, oldIndex })
    }
  })
}

onMounted(() => {
  dragSort()
  props.requestAuto && getTableList()
})

defineExpose({
  ref: tableRef,
  tableData: processTableData,
  pageParam,
  searchParam,
  searchInitParam,
  isSelected,
  selectedList,
  selectedListIds,
  enumMap,

  // 下面为 function
  getTableList,
  search,
  reset
})
</script>

<template>
  <div :class="className">
    <!-- 顶部按钮及搜索组合 -->
    <div class="pro-table-header" v-if="searchForm.length > 0 || $slots.tableHeader">
      <div class="btn-group">
        <slot
          name="tableHeader"
          :selected-list-ids="selectedListIds"
          :selected-list="selectedList"
          :is-selected="isSelected"
        />
      </div>
      <div class="search-group" v-if="searchForm.length > 0">
        <el-input v-model="searchParam.searchKey" placeholder="请输入关键字" maxlength="40">
          <template #suffix>
            <i class="iconfont icon-search" @click.stop="search" />
          </template>
        </el-input>
        <el-button v-if="searchForm.length > 1" plain @click="searchDrawerVisible = true">
          <i class="iconfont icon-filter"></i>筛选
        </el-button>
      </div>
    </div>
    <!-- 表格主体 -->
    <div class="pro-table-content">
      <el-table
        ref="tableRef"
        v-bind="{ ...$attrs, class: '' }"
        v-loading="loading"
        :id="uuid"
        :data="processTableData"
        :row-key="rowKey"
        @selection-change="selectionChange"
      >
        <!-- 默认插槽 -->
        <slot />

        <!-- 表格列遍历 -->
        <template v-for="item in tableColumns" :key="item">
          <TableColumn :column="item">
            <template v-for="slot in Object.keys($slots)" #[slot]="scope">
              <slot :name="slot" v-bind="scope" />
            </template>
          </TableColumn>
        </template>

        <!-- 无数据 -->
        <template #empty>
          <Empty />
        </template>
      </el-table>
    </div>

    <!-- 分页 -->
    <el-pagination
      v-if="isPagination"
      background
      layout="total, prev,pager,next, sizes"
      :total="pageParam.total"
      :size="size"
      :page-sizes="[30, 60, 100]"
      :page-size="pageParam.pageSize"
      :current-page="pageParam.pageNo"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />

    <!-- 搜索表单弹窗 -->
    <TableFormDrawer
      :search="search"
      :reset="reset"
      v-model="searchDrawerVisible"
      :searchForm="searchForm"
      :searchParam="searchParam"
    />
  </div>
</template>

<style lang="scss" scoped>
@import './index.scss';
</style>
